<?php

// Copyright 2019 Hackware SpA <human@hackware.cl>
// "Hackware Web Services Core" is released under the MIT License terms.

namespace Hawese\Tests;

use Hawese\Core\User;
use Hawese\Core\Token;
use Laravel\Lumen\Testing\DatabaseTransactions;

class UserTest extends TestCase
{
    use DatabaseTransactions;

    public function setUp(): void
    {
        parent::setUp();
        $this->user = new User([
            'uid' => 'user',
            'email' => 'user@domain.name',
            'password' => password_hash('password', PASSWORD_DEFAULT),
            'display_name' => 'User',
            'info' => null
        ]);
        $this->user->insert();
    }

    public function testChangePassword()
    {
        $this->user->changePassword('new_password');
        $this->assertTrue(
            password_verify('new_password', $this->user->password)
        );
    }

    public function testLogin()
    {
        $this->assertNull(app('session')->get('user_uid'));
        $this->user->login();
        $this->assertSame($this->user->uid, app('session')->get('user_uid'));
    }

    public function testLoginByPassword()
    {
        $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
        $this->assertEqualsCanonicalizing(
            $this->user,
            User::loginByPassword($this->user->uid, 'password')
        );
        $this->assertSame(app('session')->get('user_uid'), $this->user->uid);
    }

    public function testFailedLoginByPassword()
    {
        $_SERVER['REMOTE_ADDR'] = '127.0.0.240';
        $this->expectExceptionMessage('Too many failed requests');
        for ($i = 0; $i < 5; $i++) {
            try {
                User::loginByPassword($this->user->uid, 'notpassword');
            } catch (\Hawese\Core\Exceptions\WrongCredentialsException $e) {
                // nothing, continue
            }
        }
    }

    public function testLoginByPasswordRemember()
    {
        $_SERVER['REMOTE_ADDR'] = '127.0.0.1';

        $this->assertEqualsCanonicalizing(
            $this->user,
            User::loginByPassword($this->user->uid, 'password', true),
        );
        $this->assertSame($this->user->uid, app('session')->get('user_uid'));

        // I should test cookies!! ... and probably don't set cookies here
        $this->assertSame(1, Token::select()->count());
    }

    public function testLoginByTokenHumanDontRemember()
    {
        $_SERVER['REMOTE_ADDR'] = '127.0.0.1';

        $token = Token::generate(Token::HUMAN, $this->user->uid);
        $this->assertEqualsCanonicalizing(
            $this->user,
            User::loginByToken($token->key, $token->secret, false)
        );
        $this->assertSame($this->user->uid, app('session')->get('user_uid'));

        // Verify previous token has been deleted after login
        $this->assertSame(0, Token::select()->count());
    }

    public function testLoginByTokenRemember()
    {
        $_SERVER['REMOTE_ADDR'] = '127.0.0.1';

        $token = Token::generate(Token::HUMAN, $this->user->uid);
        $this->assertEqualsCanonicalizing(
            $this->user,
            User::loginByToken($token->key, $token->secret)
        );
        $this->assertSame($this->user->uid, app('session')->get('user_uid'));

        // Verify previous token has been deleted after login
        $this->assertSame(1, Token::select()->count());
        $this->assertNotSame($token->key, Token::select()->first()->key);
    }

    public function testLoginByTokenSystem()
    {
        $_SERVER['REMOTE_ADDR'] = '127.0.0.1';

        $token = Token::generate(Token::SYSTEM, $this->user->uid);
        $this->assertEqualsCanonicalizing(
            $this->user,
            // 3rd param shouldn't be considered for system tokens
            User::loginByToken($token->key, $token->secret, true)
        );

        // Verify previous token has NOT been deleted after login
        $this->assertSame($token->key, Token::select()->first()->key);
    }

    public function testFailedLoginByToken()
    {
        $_SERVER['REMOTE_ADDR'] = '127.0.0.241';

        $token = Token::generate(Token::HUMAN, $this->user->uid);

        $this->expectExceptionMessage('Too many failed requests');
        for ($i = 0; $i < 5; $i++) {
            try {
                User::loginByToken($token->key, 'notsecret');
            } catch (\Hawese\Core\Exceptions\WrongCredentialsException $e) {
                // nothing, continue
            }
        }
    }

    public function testGenerateHumanToken()
    {
        $token = $this->user->generateHumanToken();
        $this->assertInstanceOf(Token::class, $token);
        $this->assertSame(Token::HUMAN, $token->type);
    }

    public function testGenerateSystemToken()
    {
        $token = $this->user->generateSystemToken();
        $this->assertInstanceOf(Token::class, $token);
        $this->assertSame(Token::SYSTEM, $token->type);
    }

    public function testLogout()
    {
        app('session')->set('user_uid', $this->user->uid);
        $token = Token::generate(Token::HUMAN, $this->user->uid);
        $_COOKIE['auth_token'] = $token->key . ':' . $token->secret;

        $this->assertTrue($this->user->logout());
        $this->assertSame(0, Token::select()->count());
        $this->assertNull(app('session')->get('user_uid'));
    }

    public function testLogoutSystemToken()
    {
        $token = Token::generate(Token::SYSTEM, $this->user->uid);
        $_COOKIE['auth_token'] = $token->key . ':' . $token->secret;

        $this->assertFalse($this->user->logout());
    }

    public function testEmailToken()
    {
        $this->assertInstanceOf(
            \Hawese\Core\Mailer::class,
            $this->user->emailToken($this->validOrigin())
        );
    }

    public function testEmailTokenException()
    {
        $this->user->email = null;
        $this->user->update(['email']);
        $this->expectException(\PHPMailer\PHPMailer\Exception::class);
        $this->user->emailToken($this->validOrigin());
    }

    public function testEmailTokenWrongOrigin()
    {
        $this->expectExceptionMessage('Unacceptable origin');
        $this->user->emailToken('not_origin');
    }

    public function testEmailTokenRedirectTo()
    {
        $redirect_to = 'https://hackware.cl/sup?body=buddy';
        $email = $this->user->emailToken($this->validOrigin(), $redirect_to);

        $redirect_to_encoded = 'redirect_to=' . urlencode($redirect_to);
        $this->assertStringContainsString($redirect_to_encoded, $email->Body);
        $this->assertStringContainsString($redirect_to_encoded, $email->AltBody);
    }

    public function testAmI()
    {
        $this->assertFalse($this->user->amI());
        app('session')->set('user_uid', $this->user->uid);
        $this->assertTrue($this->user->amI());
    }

    public function testIsOwner()
    {
        $this->assertTrue($this->user->isOwner($this->user));

        $otherUser = new User();
        $this->assertFalse($this->user->isOwner($otherUser));
    }

    public function testIsSuperUser()
    {
        $this->assertFalse($this->user->isSuperUser());

        $user = new User(['uid' => 'hawese']);
        $this->assertTrue($user->isSuperUser());
    }

    public function testFindByEmailOrCreate()
    {
        $email = 'unknown_user@example.com';
        $this->expectException(
            \Hawese\Core\Exceptions\ModelObjectNotFoundException::class
        );
        User::find($email, 'email');
        $user = User::findByEmailOrCreate('unknown_user@example.com');
        $this->assertEquals($user, User::find($email, 'email'));
    }
}
